package clases;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Random;

public class EvalTree {
  public static final int MAX_MUTATION_HEIGHT = 5;
  // para la funcion recursiva que genera el .DOT
  private static int n;
  public EvalTree left, right;
  public String nombre;
  public int solsize;
  private int solcant;

  public EvalTree(int solcant) {
    this.left = null;
    this.right = null;
    this.solcant = solcant;
    this.nombre = null;
  }

  public void newRandomTree(int max_height, int solcant) {
    Random randy = new Random((long)(Math.random()*Integer.MAX_VALUE));
    int height = 1;
    this.solcant = solcant;
    if (max_height == 0) {
      this.nombre = "A" + randy.nextInt(5);
      return;
    } else {
      if (max_height == 1) {
        height = 0;
      } else {
        height = randy.nextInt(max_height - 1);
        // TODO prueba: 
       // height = max_height - 1;
      }
      switch (randy.nextInt(3)) {
      case 0:
        this.nombre = "AND";
        this.left = new EvalTree(solcant);
        this.left.newRandomTree(height, solcant);

        break;
      case 1:
        this.nombre = "OR";
        this.left = new EvalTree(solcant);
        this.left.newRandomTree(height, solcant);

        break;
      case 2:
        this.nombre = "NOT";
        this.left = null;
        break;
      }
      this.right = new EvalTree(solcant);
      this.right.newRandomTree(height, solcant);

    }
  }

  public String evalAllTreeInputs() throws Exception {
    // TODO esta no la revisamos
    // evalua el arbol para todas las entradas posibles, y
    // retorna un string de 32 bits con 1 si es TRUE, o 0 si es FALSE
    String output = new String();
    ArrayList<Boolean> a_i = new ArrayList<Boolean>(5);
    int input = 0;
    output = "";
    for (int i = 0; i < this.solcant; i++) {
      input = i;
      for (int j = 0; j < 5; j++) {
        a_i.add(j, (input % 2) == 1);
        input = input / 2;
      }
      try {
        if (this.eval(a_i.get(0), a_i.get(1), a_i.get(2), a_i.get(3), a_i
            .get(4)) == true) {
          output = "1" + output;
        } else {
          output = "0" + output;
        }
      } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        throw new Exception("Error en evalAllTreeInputs.");
      }
    }
    return output;
  }

  private boolean eval(Boolean a0, Boolean a1, Boolean a2, Boolean a3,
      Boolean a4) throws Exception {
    // TODO esta no la revisamos
    if (this.nombre.equals("AND")) {
      return this.left.eval(a0, a1, a2, a3, a4)
          && this.right.eval(a0, a1, a2, a3, a4);
    } else if (this.nombre.equals("OR")) {
      return this.left.eval(a0, a1, a2, a3, a4)
          || this.right.eval(a0, a1, a2, a3, a4);
    } else if (this.nombre.equals("NOT")) {
      return !this.right.eval(a0, a1, a2, a3, a4);
      // null
    } else if (this.nombre.equals("A0")) {
      return a0;
    } else if (this.nombre.equals("A1")) {
      return a1;
    } else if (this.nombre.equals("A2")) {
      return a2;
    } else if (this.nombre.equals("A3")) {
      return a3;
    } else if (this.nombre.equals("A4")) {
      return a4;
    } else {
      throw new Exception("arbol mal formado");
    }
  }

  public EvalTree clone() {
    // TODO esta no la revisamos
    EvalTree resp = new EvalTree(solcant);
    resp.nombre = new String(this.nombre);

    if (!this.isLeaf()) {
      if (this.left != null) {
        resp.left = new EvalTree(solcant);
        resp.left = this.left.clone();
      }
      resp.right = new EvalTree(solcant);
      resp.right = this.right.clone();
    }
    return resp;
  }

  private boolean isLeaf() {
    return this.right == null && this.left == null;
  }

  public void reproduceTree(EvalTree t) {
    // TODO esta no la revisamos
    EvalTree t1, t2, swap;
    Random randy = new Random((long)(Math.random()*Integer.MAX_VALUE));
    int h1, h2;

    h1 = this.getHeight();
    h2 = t.getHeight();
    swap = new EvalTree(solcant);
    t1 = this.getRandomFather(randy.nextInt(h1 + 1));
    t2 = t.getRandomFather(randy.nextInt(h2 + 1));
    swap = t1.clone();
    if (t1.nombre.equals("NOT") || randy.nextBoolean()) {
      if (t2.nombre.equals("NOT") || randy.nextBoolean()) {
        t1.right = t2.right;
        t2.right = swap.right;
      } else {
        t1.right = t2.left;
        t2.left = swap.right;
      }
    } else {
      if (t2.nombre.equals("NOT") || randy.nextBoolean()) {
        t1.left = t2.right;
        t2.right = swap.left;
      } else {
        t1.left = t2.left;
        t2.left = swap.left;
      }
    }
    return;
  }

  private EvalTree getRandomFather(int height) {
    Random randy = new Random((long)(Math.random()*Integer.MAX_VALUE));
    // TODO esta no la revisamos
    if (height == 1 || this.isLeaf() || this.right.isLeaf()
        || (this.left != null && this.left.isLeaf())) {
      return this;
    }
    if (this.left == null) {
      return this.right.getRandomFather(height - 1);
    }
    if (randy.nextBoolean()) {
      return this.left.getRandomFather(height - 1);
    } else {
      return this.right.getRandomFather(height - 1);
    }
  }

  private int getHeight() {
    // TODO esta no la revisamos
    if (this.isLeaf())
      return 0;
    else if (this.left == null && this.right != null) {
      return 1 + this.right.getHeight();
    } else if (this.right == null && this.left != null) {
      return 1 + this.left.getHeight();
    } else {
      return 1 + Math.max(this.left.getHeight(), this.right.getHeight());
    }
  }

  public void mutate() {
    Random randy = new Random((long)(Math.random()*Integer.MAX_VALUE));
    if (randy.nextDouble() < GeneticAlgorithm.P_MUT) {
      int treeHeight = this.getHeight();
      int height = 0;
      if (treeHeight > 1) {
        height = randy.nextInt(treeHeight);
      }
      this.mutateNode(height + 1);// entre 1 y height +1
    }
  }

  private void mutateNode(int nivel) {
    Random randy = new Random((long)(Math.random()*Integer.MAX_VALUE));
    // TODO esta no la revisamos
    if (this.isLeaf()) {
      this.right = new EvalTree(solcant);
      this.right.newRandomTree(EvalTree.MAX_MUTATION_HEIGHT, this.solcant);
      this.left = new EvalTree(solcant);
      this.left.newRandomTree(EvalTree.MAX_MUTATION_HEIGHT, this.solcant);

      return;
    }
    if (nivel == 1) {
      if (this.nombre.equals("NOT")) {
        this.right = new EvalTree(solcant);
        this.right.newRandomTree(EvalTree.MAX_MUTATION_HEIGHT, this.solcant);

      } else {
        if (randy.nextBoolean()) {
          this.left = new EvalTree(solcant);
          this.left.newRandomTree(EvalTree.MAX_MUTATION_HEIGHT, this.solcant);

        } else {
          this.right = new EvalTree(solcant);
          this.right.newRandomTree(EvalTree.MAX_MUTATION_HEIGHT, this.solcant);

        }
      }
      return;
    }
    if (this.nombre.equals("NOT")) {
      this.right.mutateNode(nivel - 1);
    } else {
      if (randy.nextBoolean()) {
        this.left.mutateNode(nivel - 1);
      } else {
        this.right.mutateNode(nivel - 1);
      }
    }
  }

  public void generateDOT(String path) throws FileNotFoundException {
    // TODO no revis
    FileOutputStream f = new FileOutputStream(path);
    PrintStream s = new PrintStream(f);
    s.println("digraph {\n");
    n = 0;
    printNodes(s);
    s.println("}");

    s.close();
    try {
      f.close();
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

  public void printNodes(PrintStream s) {
    // TODO no revis
    if (this.isLeaf()) {
      s.println("Node" + n + " [label=\"" + this.nombre + "\"] ;");
      EvalTree.n = EvalTree.n + 1;
    } else {
      s.println("Node" + n + " [label=\"" + this.nombre + "\"] ;");
      int aux = EvalTree.n;
      EvalTree.n = EvalTree.n + 1;
      this.right.printNodes(s);
      s.println("Node" + aux + " -> Node" + (aux + 1));

      if (this.left != null) {
        int aux2 = n;
        this.left.printNodes(s);
        s.println("Node" + aux + " -> Node" + aux2);
      }
    }
  }
}
